package download
import(
	"fmt"
    "io"
    // "io/ioutil"
    "nightowl/logger"
    . "nightowl/common"
    "net/http"
    "sync"
    "os"
)
type DownloadStatus int
const(
    PENDING DownloadStatus = iota
    DOWNLOADING
    SUCCESS
    ERROR
)
type DownloadItem struct{
    Index           int     //索引
    Tag             string  //tag
    Url             string  //下载地址
    BasicAuth       map[string]string
    CheckCode       string  //校验值
    DestPath        string  //目标存储地址
    Status          DownloadStatus  //状态， PENDING/DOWNLOADING/ERROR/SUCCESS
    Msg             interface{}
}
type DownloadManager struct{
    Items                   []*DownloadItem      //下载项目
    Concurrency             int                 //并发数
    Mutex                   sync.Mutex
    ItemDoneCallback        DownloadItemCallback
    DoneCallback            DownloadDoneCallback
}
type DownloadDoneCallback func(d *DownloadManager)
type DownloadItemCallback func(d *DownloadManager, item *DownloadItem)

func getFileSize(fp string) int64{
    fileInfo, err := os.Stat(fp)
    if err != nil{
        fmt.Println("read file error:", err)
        return 0
    }
    return fileInfo.Size()
}
func NewDownloadManager(concurrency int, itemDoneCallback DownloadItemCallback, doneCallback DownloadDoneCallback) *DownloadManager{
    d := &DownloadManager{
        Concurrency: concurrency,
        ItemDoneCallback: itemDoneCallback,
        DoneCallback: doneCallback,
    }
    return d
}
func (d *DownloadManager) Enqueue(tag string, url string, basicAuth map[string]string, destPath string){
    item := &DownloadItem{
        Index: len(d.Items),
        Tag: tag,
        Url: url,
        BasicAuth: basicAuth,
        DestPath: destPath,
        Status: PENDING,
        Msg: "",
    }
    d.Items = append(d.Items, item)
    d.next()    
}
func (d *DownloadManager) download(item *DownloadItem) *DownloadItem{
    defer func(){        
        if item.Status == SUCCESS{
            sha256Str := Sha256File(item.DestPath)
            logger.Info("download|status:%d|url:%s|destPath:%s|sha256:%s|size:%d", item.Status, item.Url,item.DestPath, sha256Str, getFileSize(item.DestPath))
        }else{            
            logger.Error("download|status:%d|url:%s|destPath:%s|err:%s", item.Status, item.Url,item.DestPath, item.Msg)
        }
        d.next()
        d.itemDownloadFinished(item)
    }()
    //开始下载
    client := &http.Client{
        Transport: &http.Transport{},
    }
    req, err := http.NewRequest("GET", item.Url, nil)
    if err != nil {
        // logger.Error("1downloaded|status:%d|url:%s|error:%s", ERROR, item.Url, err)
        // logger.Error("1 download|status:%d|url:%s|destPath:%s|size:%d|error:%s", item.Status, item.Url,item.DestPath,0, err)
        item.Status = ERROR
        item.Msg = err
        return item
    }
    if item.BasicAuth != nil{        
        req.SetBasicAuth(item.BasicAuth["username"], item.BasicAuth["password"])
        logger.Info("k:%s,v:%s",item.BasicAuth["username"], item.BasicAuth["password"])
    }

    // //创建 HTTP GET 请求
    // resp, err := http.Get(item.Url)
    resp, err := client.Do(req)
    if err != nil {
        // logger.Error("2 download|status:%d|url:%s|destPath:%s|size:%d|error:%s", item.Status, item.Url,item.DestPath,0, err)
        item.Status = ERROR
        item.Msg = err
        return item
    }
    defer resp.Body.Close()
    if resp.StatusCode != http.StatusOK{        
        // logger.Error("1downloaded|status:%d|url:%s|http status:%s", ERROR, item.Url, resp.Status)
        // logger.Error("3 download|status:%d|url:%s|destPath:%s|size:%d|error:%s", item.Status, item.Url,item.DestPath,0, resp.Status)
        item.Status = ERROR
        item.Msg = resp.Status
        return item
    }

    // 创建本地文件
    destFile, err := os.Create(item.DestPath)
    if err != nil {        
        // logger.Error("4 download|status:%d|url:%s|destPath:%s|size:%d|error:%s", item.Status, item.Url,item.DestPath,0, err)
        item.Status = ERROR
        item.Msg = err
        return item
    }
    defer destFile.Close()

    // 将下载的内容写入本地文件
    _, err = io.Copy(destFile, resp.Body)
    if err != nil {
        // logger.Error("3downloaded|status:%d|url:%s|error:%s", ERROR, item.Url, err)
        // logger.Error("5 download|status:%d|url:%s|destPath:%s|size:%d|error:%s", item.Status, item.Url,item.DestPath,0, err)
        item.Status = ERROR
        item.Msg = err
        return item
    }
    // logger.Error("6 download|status:%d|url:%s|destPath:%s|size:%d|error:%s", item.Status, item.Url,item.DestPath, getFileSize(item.DestPath), nil)
    item.Status = SUCCESS
    return item
}
func (d *DownloadManager) next(){
    // logger.Info("=====LOCK1=====")
    // 加锁
    if d.Mutex.TryLock(){
        // logger.Info("=====LOCK2=====")
        n := 0
        for _, fileItem := range d.Items{
            // logger.Info("=====next|%d|file:%s|status:%d",i, fileItem.DestPath, fileItem.Status)
            if fileItem.Status == DOWNLOADING {
                n = n+1
            }
            if fileItem.Status == PENDING{
                fileItem.Status = DOWNLOADING
                // logger.Info("+++next|%d|file:%s|status:%d,%d", i, fileItem.DestPath, fileItem.Status, DOWNLOADING)       
                logger.Info("download start|status:%d|url:%s|destPath:%s", fileItem.Status, fileItem.Url,fileItem.DestPath)         
                go d.download(fileItem) 
                n = n + 1
            }
        }
        // logger.Info("===============downloading:%d", n)
        if n == 0 {
            d.done()
        }
        defer d.Mutex.Unlock()
    }
    // logger.Info("=====LOCK3=====")
}
func (d *DownloadManager) itemDownloadFinished(item *DownloadItem) {    
    if d.ItemDoneCallback != nil {
        d.ItemDoneCallback(d, item)
    }
}
func (d *DownloadManager) done(){
    if d.DoneCallback != nil {
        d.DoneCallback(d)
    }
}
